package server.service;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.net.SocketAddress;

/**
 *   用于对网络事件进行读写的操作  适配器
 */
public class TimeServerHandler extends ChannelInboundHandlerAdapter {


    /**
     *  接收到客户端的消息会自动触发,该方法  是  此类继承ChannelInboundHandlerAdapter  时 调用的 如果是继承
     *  SimpleChannelInboundHandler<T>  则会 调用  channelRead0方法   实际上都差不多
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //将 msg 转为 Netty 的 ByteBuf 对象，类似 JDK 中的 java.nio.ByteBuffer，不过 ButeBuf 功能更强，更灵活
        ByteBuf buf = (ByteBuf) msg;
        // readableBytes : 获取缓冲区可读字节数,然后创建字节数组, 避免盲目创建定长大小的数组, 造成资源浪费
        byte[] reg = new byte[buf.readableBytes()];
        //将缓冲字节数组复制到新建的byte数组中,然后将字节数组转换为字符串
        buf.readBytes(reg);
        //指定字符编码  utf-8
        String body = new String(reg,"UTF-8");
        System.out.println("接收到客户端的消息:"+body+"\n"+ "当前线程名称:"+Thread.currentThread().getName());

        /**
         * 回复消息
         * copiedBuffer: 创建一个新的缓冲区,内容为里面的参数
         * 通过 ChannelHandlerContext 的write方法,将消息异步发送给客户端
         */
        String response = "服务端已经接收到消息---并返回:";
        ByteBuf resByteBuf = Unpooled.copiedBuffer(response.getBytes());
        ctx.writeAndFlush(resByteBuf);
    }

    /**
     * 当初始化后会调用channelActive方法   变为活跃状态（连接到了远程主机），可以接受和发送数据
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[服务器] 用户"+ctx.channel().remoteAddress()+"上线");
    }

    /**
     *  客户端与服务器端建立链接的时候调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //当有客户端链接的时候 会调用此方法, 那么就在这里将  channel 添加到channelGroup中,
        Channel channel = ctx.channel();
        System.out.println("[服务器] 用户"+channel.remoteAddress()+ "已建立链接");
    }

    /**
     * 客户端与服务端失去连接调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[服务器] 用户"+ctx.channel().remoteAddress() +"已断开链接");
        //关闭链接
       ctx.close();
    }



    /**
     *  注册器,注册时调用  channel注册到一个EventLoop。
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
    }

    /**
     *  channel已经创建，但是未注册到一个EventLoop里面，也就是没有和Selector绑定
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        super.channelUnregistered(ctx);
    }



    /**
     * 频道无效时调用  channel处于非活跃状态，没有连接到远程主机
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[服务器]:"+ctx.channel().remoteAddress()+"下线");
        super.channelInactive(ctx);
    }





    /**
     * 发送消息时调用
     * flush: 将消息发送队列中的消息  写入 SocketChannel 中发送给对方,为了频繁的唤醒Selector 进行消息发送
     * Netty的 write方法并不会直接将消息写入SocketChannel中,调用write 只是把待发送的消息 放到缓存数据中,再通过 flush 方法 将发送缓冲区的消息全部写入SocketChannel 中
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete");
        ctx.flush();
    }

    /**
     * 用户事件触发  也就是说 所有的链接都会走这一个方法
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        //如果用来做心跳检测 那么就 判断 这个事件是不是 空闲检测事件
        System.out.println("userEventTriggered()");
        if(evt instanceof IdleStateEvent){
            IdleStateEvent idle = (IdleStateEvent)evt;
            IdleState state = idle.state();
            String s = null;
            switch(state){
                case READER_IDLE: s =  "读空闲" ; break;
                case WRITER_IDLE: s = "写空闲" ;break;
                case ALL_IDLE: s = "读写空闲";break;
                default: return;
            }
            System.out.println(s);
        }
        //如果检测到空闲  则关闭链接
        ctx.close();
    }

    /**
     * 当Channel的可写状态发生改变时被调用。用户可以确保写操作不会完成的太快(以避免发生OutOfMemoryError)
     * 或者可以在Channel变为再次可写时恢复写入。Channel的isWriteable()方法可以来检测Channel的可写性。
     * 与可写性相关的阀值可以通过Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法来设置
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelWritabilityChanged");
        super.channelWritabilityChanged(ctx);
    }

    /**
     * 当发生异常时调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //当发生异常时  会访问此方法, 在此处  可以对异常进行处理, 也可以通过 ChannelHandlerContext 进行 如  close(关闭) 等操作
        ctx.close();
    }
}
